package com.aps.algorithm.heuristic.genetic;

import cn.hutool.core.collection.CollUtil;
import com.aps.algorithm.heuristic.genetic.dto.ApsOperationDto;
import com.aps.algorithm.heuristic.genetic.dto.Interval;
import com.aps.algorithm.heuristic.genetic.model.AbstractCalculateFitness;
import com.aps.algorithm.heuristic.genetic.model.Chromosome;
import com.aps.common.model.genetic.Equipment;
import com.aps.common.model.genetic.Order;
import com.aps.common.model.genetic.OrderProcess;
import com.aps.common.model.genetic.ProcessInterval;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.Assert;

import java.time.Duration;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @Author nbZhuozh
 * @Date 2025-09-10  14:09
 * <p>
 * <p>
 * 生产方式：默认 订单拉动
 * 1、加载种群
 * 2、编排每一种生产顺序的 开始时间、结束时间、使用设备
 * 3、结合【硬约束】、【软约束】进行 染色体打分
 **/
@Data
@Slf4j
public class ApsDefaultCalculateFitness extends AbstractCalculateFitness<ApsOperationDto> {

    private ProduceType produceType;//生产方式：库存拉动、订单拉动 （目前只做订单）

    private Map<String, List<Interval>> machinePlanCacheMap; //设备计划表 用来判断设备是否被占用。 key：设备id ， v：时间区间段

//    private ApsModel apsModel; //订单数据、约束数据
//
//    public ApsDefaultCalculateFitness(ApsModel apsModel, ProduceType produceType) {
//        this.apsModel = apsModel;
//        this.produceType = produceType;
//    }

    private static final int OVERTIME_START_HOUR = 18;
    private static final int OVERTIME_END_HOUR = 20;
    private static final int WORK_START_HOUR = 8;
    private static final int MORNING_END_HOUR = 12;
    private static final int AFTERNOON_START_HOUR = 13;
    private static final int WORK_END_HOUR = 18;

    /**
     * 前置处理
     */
    private void beforeArrange(List<ApsOperationDto> genes) {
        //重置 设备使用表
        this.machinePlanCacheMap = new HashMap<>();
    }

    /**
     * 业务排期
     */
    private void afterArrange() {

    }

    public void arrange(List<ApsOperationDto> genes) {
        //约束配置
        //按照顺序给工序分配开始时间
        //获取订单信息（产品数量 ， 交付时间）
        //分配设备 （查询设备是否占用，分配设备）
        //工序排期时间段 （按照工序标准工时、产品数量、设备产能）


        //给第一个工序的 赋值开始日期  ps: 第一个工序的标准：genes中 不存在上一个工序
        for (ApsOperationDto gene : genes) {

        }
    }

    private double calFintness() {
        return 0.0;
    }

    private Map<String, List<Equipment>> machinesByType;
    LocalDateTime globalStartTime = LocalDateTime.of(2024, 6, 1, 8, 0); // 2024-06-01 08:00

    private final Map<String, Order> orderMap = new HashMap<>();

    {
        orderMap.put("1", new Order("1", 10, LocalDateTime.of(2024, 6, 1, 8, 0), LocalDateTime.of(2024, 6, 1, 9, 0), null));
        orderMap.put("2", new Order("2", 15, LocalDateTime.of(2024, 6, 1, 8, 0), LocalDateTime.of(2024, 6, 1, 9, 0), null));
        orderMap.put("3", new Order("3", 20, LocalDateTime.of(2024, 6, 1, 8, 0), LocalDateTime.of(2024, 6, 1, 9, 0), null));
    }

    /**
     * 处理 染色体种群
     *
     * @param chromosomes
     */
    @Override
    public void excute(List<Chromosome> chromosomes) {
        //订单参数下面使用，有数据库之后这一步其实是不需要的

        // 设置所有订单的统一开始时间
        // 创建设备（支持多台同类型设备）
        List<Equipment> machines = new ArrayList<>();
        // 添加2台M1类型设备
//        machines.add(new Equipment("M1-1", "M1", globalStartTime));
//        machines.add(new Equipment("M1-2", "M1", globalStartTime));
//        // 添加2台M2类型设备
//        machines.add(new Equipment("M2-1", "M2", globalStartTime));
//        machines.add(new Equipment("M2-2", "M2", globalStartTime));
//        // 添加1台M3类型设备
//        machines.add(new Equipment("M3-1", "M3", globalStartTime));
        this.machinesByType = machines.stream()
                .collect(Collectors.groupingBy(Equipment::getType));
        Assert.notNull(chromosomes,
                "chromosomes must not be null");
//        Assert.notNull(this.apsModel,
//                "apsModel must not be null");
        for (Chromosome<ApsOperationDto> chromosome : chromosomes) {
            //获取种群中的染色体
            List<ApsOperationDto> geneSequence = chromosome.getGeneSequence();
            //计算染色体适应度
            chromosome.setFitness(calSingleChromosome(geneSequence));
        }
    }

    /**
     * @description: 选择最早可用且能满足时间要求的设备
     * @author: jsy
     * @time: 2025/9/13 14:40
     */
    private Equipment selectBestMachine(OrderProcess op, LocalDateTime earliestPossibleStart) {
        List<Equipment> candidateMachines = machinesByType.get(op.getEquipmentType());
        if (candidateMachines == null || candidateMachines.isEmpty()) {
            return null;
        }

        // 选择最早可用且能满足时间要求的设备
        return candidateMachines.stream()
                .min(Comparator.comparing(machine -> {
                    LocalDateTime machineAvailable = machine.getNextAvailableTime();
                    return Duration.between(earliestPossibleStart,
                                    machineAvailable.isAfter(earliestPossibleStart) ? machineAvailable : earliestPossibleStart)
                            .toMinutes();
                }))
                .orElse(null);
    }

    /**
     * @description: 计算染色体适应度
     * @author: jsy
     * @time: 2025/9/16 10:31
     */
    public double calSingleChromosome(List<ApsOperationDto> genes) {
//        //编排前，前置处理
//        beforeArrange(genes);
//        //基于染色体序列编排  NOTES： 在这里给染色体分配 设备、设备执行时间段
//        arrange(genes);
//        //编排后，数据处理
//        afterArrange();
//        //约束打分处理
//        return calFintness();

        //本地订单下一个工序的可执行时间
        //记录每个订单下一个工序的可执行的时间
        Map<String, LocalDateTime> lastOperationEndTime = new HashMap<>();
        for (ApsOperationDto apsOp : genes) {
            LocalDateTime orderLastEndTime = lastOperationEndTime.getOrDefault(apsOp.getOrderProcess().getOrderId(), globalStartTime);
            //选择合适的设备
            Equipment selectedMachine = selectBestMachine(apsOp.getOrderProcess(), orderLastEndTime);
            if (selectedMachine == null) {
                return 0; // 无可用设备，适应度为0
            }
            //工序开始时间
            LocalDateTime earliestPossibleStart = selectedMachine.getNextAvailableTime();
            LocalDateTime currentTime = earliestPossibleStart;
            for (int i = 0; i < orderMap.get(apsOp.getOrderProcess().getOrderId()).getQuantity(); i++) {
                //计算工序的结束时间
                LocalDateTime productEndTime = calculateEndTime(currentTime, apsOp.getOrderProcess().getUnitProcessingTime());
                //计算时间区间
                ProcessInterval processInterval = new ProcessInterval(apsOp.getOrderProcess().getId(), currentTime, productEndTime, i + 1, selectedMachine.getId());
                //添加基因时间区间列表
                apsOp.getOrderProcess().addProcessInterval(processInterval);
                //更新这个基因的开始时间
                currentTime = productEndTime;
            }
            LocalDateTime startTime = null;
            //基因的结束时间
            LocalDateTime endTime = null;
            if (CollUtil.isNotEmpty(apsOp.getOrderProcess().getProcessIntervalList())) {
                //基因的开始时间
                startTime = apsOp.getOrderProcess().getProcessIntervalList().get(0).getStartTime();
                //基因的结束时间
                endTime = apsOp.getOrderProcess().getProcessIntervalList().get(apsOp.getOrderProcess().getProcessIntervalList().size() - 1).getEndTime();
            }
            //是否超过交付日期。超过的适应度为0 直接返回 代表整个染色体不可用
            if (endTime.isAfter(orderMap.get(apsOp.getOrderProcess().getOrderId()).getDueDate())) {
                return 0;
            }
            //选中的机器更新下一个可用的时间
            selectedMachine.setNextAvailableTime(endTime);
            //更新这个订单的下一个工序的开始时间
            lastOperationEndTime.put(apsOp.getOrderProcess().getOrderId(), endTime);
        }

        return 1;
    }

    /**
     * @description: 计算结束时间，考虑休息时间
     * @author: jsy
     * @time: 2025/9/13 15:03
     */
    private LocalDateTime calculateEndTime(LocalDateTime startTime, double processingTime) {
        if (processingTime <= 0) {
            return startTime;
        }

        LocalDateTime currentTime = startTime;
        double remainingTime = processingTime;

        while (remainingTime > 0) {
            if (isRestTime(currentTime)) {
                currentTime = currentTime.withHour(AFTERNOON_START_HOUR).withMinute(0);
                continue;
            }

            if (isNonWorkingTime(currentTime)) {
                currentTime = getNextWorkingPeriodStart(currentTime);
                continue;
            }

            double timeInCurrentPeriod = getRemainingTimeInPeriod(currentTime);
            double processInThisPeriod = Math.min(remainingTime, timeInCurrentPeriod);

            currentTime = currentTime.plusMinutes((long) processInThisPeriod);
            remainingTime -= processInThisPeriod;
        }

        return currentTime;
    }

    /**
     * @description: 获取下一个工作时间段的开始时间
     * @author: jsy
     * @time: 2025/9/13 15:19
     */
    private LocalDateTime getNextWorkingPeriodStart(LocalDateTime time) {
        int hour = time.getHour();

        if (hour < WORK_START_HOUR) {
            return time.withHour(WORK_START_HOUR).withMinute(0);
        } else if (hour >= MORNING_END_HOUR && hour < AFTERNOON_START_HOUR) {
            return time.withHour(AFTERNOON_START_HOUR).withMinute(0);
        } else {
            return time.plusDays(1).withHour(WORK_START_HOUR).withMinute(0);
        }
    }

    /**
     * @description: 获取当前时间段的剩余工作时间
     * @author: jsy
     * @time: 2025/9/13 15:19
     */
    private double getRemainingTimeInPeriod(LocalDateTime time) {
        int hour = time.getHour();
        int minute = time.getMinute();

        if (hour < WORK_START_HOUR) {
            return 0;
        } else if (hour < MORNING_END_HOUR) {
            return (MORNING_END_HOUR - hour) * 60 - minute;
        } else if (hour == MORNING_END_HOUR) {
            return 0;
        } else if (hour < WORK_END_HOUR) {
            return (WORK_END_HOUR - hour) * 60 - minute;
        } else if (hour < OVERTIME_END_HOUR) {
            return (OVERTIME_END_HOUR - hour) * 60 - minute;
        } else {
            return 0;
        }
    }

    /**
     * @description: 检查是否为工作时间
     * @author: jsy
     * @time: 2025/9/13 15:05
     */
    private boolean isNonWorkingTime(LocalDateTime time) {
        int hour = time.getHour();
        return hour < WORK_START_HOUR || hour >= OVERTIME_END_HOUR || isRestTime(time);
    }

    /**
     * @description: 检查是否为休息时间（午休）
     * @author: jsy
     * @time: 2025/9/13 15:04
     */
    private boolean isRestTime(LocalDateTime time) {
        int hour = time.getHour();
        return hour == MORNING_END_HOUR || (hour == MORNING_END_HOUR && time.getMinute() > 0);
    }

    public enum ProduceType {
        MTO, //工序拉动生产
        MTS //库存拉动生产  TODO 这种以后再做，但需要考虑这种编排方案。
    }


}

